Numpy 介紹

In [1]:
# 起手式
import numpy as np

建立 ndarray

In [2]:
np.array([1,2,3,4])
Out[2]:
array([1, 2, 3, 4])
In [3]:
x = _
In [4]:
y = np.array([[1.,2,3],[4,5,6]])
y
Out[4]:
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])

看 ndarray 的第一件事情: shape , dtype

In [5]:
x.shape
Out[5]:
(4,)
In [6]:
y.shape
Out[6]:
(2, 3)
In [7]:
x.dtype
Out[7]:
dtype('int64')
In [8]:
y.dtype
Out[8]:
dtype('float64')

有時候,可以看圖

In [9]:
# import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
# 畫圖
plt.plot(x, 'x');

有很多其他建立的方式

In [10]:
# 建立 0 array
np.zeros_like(y)
Out[10]:
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
In [11]:
np.zeros((10,10))
Out[11]:
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])
In [12]:
# 跟 range 差不多
x = np.arange(0, 10, 0.1)
# 亂數
y = np.random.uniform(-1,1, size=x.shape)
plt.plot(x, y)
Out[12]:
[<matplotlib.lines.Line2D at 0x7f57ac1dab70>]

這是一堆資料

  • 資料有什麼資訊?
  • 資料有什麼限制?
  • 這些限制有什麼意義?好處?
  • 以前碰過什麼類似的東西?
  • 可以套用在哪些東西上面?
  • 可以怎麼用(運算)?

最簡單的計算是 逐項計算 see also np.vectorize

In [13]:
x = np.linspace(0, 2* np.pi, 1000)
plt.plot(x, np.sin(x))
Out[13]:
[<matplotlib.lines.Line2D at 0x7f57ac15e0f0>]

Q0:

畫出 $y=x^2+1$ 或其他函數的圖形 用

plt.plot?

看看 plot 還有什麼參數可以玩

In [14]:
#可以用 %run -i 跑參考範例
%run -i q0.py
In [15]:
# 或者看看參考範例
#%load q0.py

Q1:

試試看圖片。 使用

from PIL import Image
# 讀入 PIL Image (這張圖是從 openclipart 來的 cc0)
img = Image.open('img/Green-Rolling-Hills-Landscape-800px.png')
# 圖片轉成 ndarray
img_array = np.array(img)
# ndarray 轉成 PIL Image
Image.fromarray(img_array)

看看這個圖片的內容, dtype 和 shape

In [16]:
# 參考答案
#%load q1.py 

Indexing

可以用類似 list 的 indexing

In [17]:
a = np.arange(30)
a
Out[17]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
In [18]:
a[5]
Out[18]:
5
In [19]:
a[3:7]
Out[19]:
array([3, 4, 5, 6])
In [20]:
# 列出所有奇數項
a[1::2]
Out[20]:
array([ 1,  3,  5,  7,  9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29])
In [21]:
# 還可以用來設定值
a[1::2]  = -1
a
Out[21]:
array([ 0, -1,  2, -1,  4, -1,  6, -1,  8, -1, 10, -1, 12, -1, 14, -1, 16,
       -1, 18, -1, 20, -1, 22, -1, 24, -1, 26, -1, 28, -1])
In [22]:
# 或是
a[1::2] = -a[::2]-1
a
Out[22]:
array([  0,  -1,   2,  -3,   4,  -5,   6,  -7,   8,  -9,  10, -11,  12,
       -13,  14, -15,  16, -17,  18, -19,  20, -21,  22, -23,  24, -25,
        26, -27,  28, -29])

Q2

給定

x = np.arange(30)
a = np.arange(30)
a[1::2] = -a[1::2]

畫出下面的圖

In [23]:
%run -i q2.py
#%load q2.py

ndarray 也可以

In [24]:
b = np.array([[1,2,3], [4,5,6], [7,8,9]])
b
Out[24]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
In [25]:
b[1][2]
Out[25]:
6
In [26]:
b[1,2]
Out[26]:
6
In [27]:
b[1]
Out[27]:
array([4, 5, 6])

Q3

動手試試看各種情況 比方

b = np.random.randint(0,99, size=(10,10))
b[::2, 2]

Fancy indexing

In [28]:
b = np.random.randint(0,99, size=(5,10))
b
Out[28]:
array([[13, 26, 41, 43, 56, 54, 93, 55, 36, 82],
       [89, 74, 15, 21, 10, 26, 65, 94, 66,  7],
       [54, 80, 58, 25, 64, 84, 38, 31, 82, 92],
       [30, 60, 54, 97, 45, 92,  9, 48, 15,  2],
       [73, 92, 42, 44, 33, 45, 46,  4, 26, 26]])

試試看下面的結果

想一下是怎麼一回事(numpy 在想什麼?)

In [29]:
b[[1,3]]
Out[29]:
array([[89, 74, 15, 21, 10, 26, 65, 94, 66,  7],
       [30, 60, 54, 97, 45, 92,  9, 48, 15,  2]])
In [30]:
b[(1,3)]
Out[30]:
21
In [31]:
b[[1,2], [3,4]]
Out[31]:
array([21, 64])
In [32]:
b[[(1,2),(3,4)]]
Out[32]:
array([21, 64])
In [33]:
b[[True, False, False, True, False]]
Out[33]:
array([[13, 26, 41, 43, 56, 54, 93, 55, 36, 82],
       [30, 60, 54, 97, 45, 92,  9, 48, 15,  2]])

Q4

b 中的偶數都變成 -1

In [34]:
#參考範例
%run -i q4.py
array([[13, -1, 41, 43, -1, -1, 93, 55, -1, -1],
       [89, -1, 15, 21, -1, -1, 65, -1, -1,  7],
       [-1, -1, -1, 25, -1, -1, -1, 31, -1, -1],
       [-1, -1, -1, 97, 45, -1,  9, -1, 15, -1],
       [73, -1, -1, -1, 33, 45, -1, -1, -1, -1]])

用圖形來練習

In [35]:
# 還記得剛才的
from PIL import Image
img = Image.open('img/Green-Rolling-Hills-Landscape-800px.png')
img_array = np.array(img)
Image.fromarray(img_array)
Out[35]:
In [36]:
# 用來顯示圖片的函數
from IPython.display import display
def show(img_array):
    display(Image.fromarray(img_array))

Q

  • 將圖片縮小成一半
  • 擷取中間一小塊
  • 圖片上下顛倒
  • 左右鏡射
  • 去掉綠色
  • 將圖片放大兩倍
  • 貼另外一張圖到大圖中
    from urllib.request import urlopen
    url = "https://raw.githubusercontent.com/playcanvas/engine/master/examples/images/animation.png"
    simg = Image.open(urlopen(url))
    
  • 紅綠交換
  • 團片變成黑白 參考 Y=0.299R+0.587G+0.114B
    • 會碰到什麼困難? 要如何解決
In [37]:
# 將圖片縮小成一半
%run -i q_half.py
In [38]:
# 將圖片放大
%run -i q_scale2.py
原始大小
放大兩倍
In [39]:
# 圖片上下顛倒
show(img_array[::-1])
In [40]:
%run -i q_paste.py
簡單的
這樣呢?
In [41]:
%run -i q_grayscale.py

Q

  • 挖掉個圓圈? (300,300)中心,半徑 100
  • 旋轉九十度? x,y 互換?
In [42]:
# 用迴圈畫圓
%run -i q_slow_circle.py
In [43]:
# 用 fancy index 畫圓
%run -i q_fast_circle.py

indexing 的其他用法

In [44]:
# 還可以做模糊化
a = img_array.astype(float)
for i in range(10):
    a[1:,1:] = (a[1:,1:]+a[:-1,1:]+a[1:,:-1]+a[:-1,:-1])/4
show(a.astype('uint8'))
In [45]:
# 求邊界
a = img_array.astype(float)
a = a @ [0.299, 0.587, 0.114, 0]
a = np.abs((a[1:]-a[:-1]))*2
show(a.astype('uint8'))

Reshaping

.flatten 拉平看看資料在電腦中如何儲存?

查看 .reshape, .T, np.rot00, .swapaxes .rollaxis 然後再做一下上面的事情

In [46]:
# reshaping 的應用
R,G,B,A = img_array.reshape(-1,4).T
plt.hist((R,G,B,A), color="rgby");

堆疊在一起

查看 np.vstack np.hstack np.concatenate 然後試試看

In [47]:
# 例子
show(np.hstack([img_array, img_array2]))
In [48]:
# 例子
np.concatenate([img_array, img_array2], axis=2).shape
Out[48]:
(530, 800, 8)

作用在整個 array/axis 的函數

In [49]:
np.max([1,2,3,4])
Out[49]:
4
In [50]:
np.sum([1,2,3,4])
Out[50]:
10
In [51]:
np.mean([1,2,3,4])
Out[51]:
2.5
In [52]:
np.min([1,2,3,4])
Out[52]:
1

多重意義的運用, 水平平均,整合垂直平均

In [53]:
x_mean = img_array.astype(float).min(axis=0, keepdims=True)
print(x_mean.dtype, x_mean.shape)
y_mean = img_array.astype(float).min(axis=1, keepdims=True)
print(y_mean.dtype, y_mean.shape)
# 自動 broadcast 
xy_combined = ((x_mean+y_mean)/2).astype('uint8')
show(xy_combined)
float64 (1, 800, 4)
float64 (530, 1, 4)

Tensor 乘法

先從點積開始

In [54]:
# = 1*4 + 2*5 + 4*6
np.dot([1,2,3], [4,5,6])
Out[54]:
32
In [55]:
u=np.array([1,2,3])
v=np.array([4,5,6])
print( u@v )
print( (u*v).sum() )
32
32

矩陣乘法

如果忘記矩陣乘法是什麼了, 參考這裡 http://matrixmultiplication.xyz/ 或者 http://eli.thegreenplace.net/2015/visualizing-matrix-multiplication-as-a-linear-combination/

矩陣乘法可以看成是:

  • 所有組合(其他軸)的內積(共有軸)
  • 多個行向量線性組合
  • 代入線性方程式 A1-矩陣與基本列運算.ipynb
  • 用 numpy 來理解
    np.sum(a[:,:, np.newaxis] * b[np.newaxis, : , :], axis=1)
    dot(a, b)[i,k] = sum(a[i,:] * b[:, k])
    

高維度

要如何推廣?

  • tensordot, tensor contraction, a.shape=(3,4,5), b.shape=(4,5,6), axis = 2 時等價於

    np.sum(a[..., np.newaxis] * b[np.newaxis, ...], axis=(1, 2))
    tensordot(a,b)[i,k]=sum(a[i, ...]* b[..., k])
    

    https://en.wikipedia.org/wiki/Tensor_contraction

  • dot

    dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])
    np.tensordot(a,b, axes=(-1,-2))
    
  • matmul 最後兩個 index 當成 matrix
    a=np.random.random(size=(3,4,5))
    b=np.random.random(size=(3,5,7))
    (a @ b).shape
    np.sum(a[..., np.newaxis] * np.moveaxis(b[..., np.newaxis], -1,-3), axis=-2)
    
  • einsum https://en.wikipedia.org/wiki/Einstein_notation
    np.einsum('ii', a) # trace(a)
    np.einsum('ii->i', a) #diag(a)
    np.einsum('ijk,jkl', a, b) # tensordot(a,b)
    np.einsum('ijk,ikl->ijl', a,b ) # matmul(a,b)
    
In [56]:
A=np.random.randint(0,10, size=(5,3))
A
Out[56]:
array([[0, 6, 0],
       [9, 8, 1],
       [1, 3, 6],
       [4, 6, 3],
       [4, 8, 8]])
In [57]:
B=np.random.randint(0,10, size=(3,7))
B
Out[57]:
array([[3, 2, 1, 8, 4, 4, 6],
       [2, 0, 1, 6, 5, 0, 3],
       [6, 3, 5, 1, 7, 9, 4]])
In [58]:
A.dot(B)
Out[58]:
array([[ 12,   0,   6,  36,  30,   0,  18],
       [ 49,  21,  22, 121,  83,  45,  82],
       [ 45,  20,  34,  32,  61,  58,  39],
       [ 42,  17,  25,  71,  67,  43,  54],
       [ 76,  32,  52,  88, 112,  88,  80]])

Q

  • 手動算算看 A,B 的 dot
  • 試試看其他的乘法

小結

numpy 以 ndarray 為中心

  • 最基本的運算是逐項運算(用 np.vectorize把一般的函數變成逐項運算 )
  • indexing 很好用
  • reshaping
  • 整合的操作與計算